package base;


import graphics.EnvironmentView;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import metrics.*;

import strategies.Strategy;
import strategies.StrategyFactory;
import strategies.StrategyGenerator;
import utils.Debug;
import agents.AdaptiveAgent;
import agents.Agent;
import agents.StaticAgent;

import static base.Environment.Configurations.*;

/**
 * The execution entry point class.
 */
public class Main {	
	
	private static final int NORM_HISTORY_SIZE = 10;
	private static final double NORM_HISTORY_EPSILON = 1D;
	
	private static ArrayList<Double> currentUtilities;
	
	
	/**
	 * Generates a list of agents.
	 * 
	 * @param env the environment
	 * @param staticRatios the ratios to use for the static agents - 
	 * 			multiple ratios for multiple strategies
	 * 
	 * @return the generated agents
	 * @throws Exception 
	 */
	private static List<Agent> genAgents(Environment env, int[][] staticRatios) throws Exception {
		
		List<Agent> agents = new LinkedList<Agent>();
		
		for (int i = 0; i < env.getInt(STATIC_AGENTS); i++) {			
			Strategy[] strategies = new Strategy[staticRatios.length];
			StrategyGenerator gen =
					StrategyFactory.newGenerator(env, env.getString(STRATEGY_GENERATOR));
			
			for (int j=0; j < staticRatios.length; j++) {
				strategies[j] = gen.genStrategy(staticRatios[j]);
			}
			agents.add(new StaticAgent(env.getInt(QUOTA), env,
					StrategyFactory.newSelector(env, env.getString(STATIC_SELECTOR)), strategies));
		}
		
		for (int i = 0; i < env.getInt(ADAPTIVE_AGENTS); i++) {
			StrategyGenerator gen =
					StrategyFactory.newGenerator(env, env.getString(STRATEGY_GENERATOR));
			agents.add(new AdaptiveAgent(env.getInt(QUOTA), env.getDouble(ADAPTIVE_QUOTA_FACTOR),
					env, StrategyFactory.newSelector(env, env.getString(ADAPTIVE_SELECTOR)), gen));
		}
		
		return agents;
	}
	
	/**
	 * Generate list of metrics used to evaluate the system's performance
	 * 
	 * @param env the environment
	 * @param agents the agents in the environment
	 * 
	 * @return the generated metrics
	 */
	private static List<Metric> genMetrics(Environment env, List<Agent> agents){
		List<Metric> result = new LinkedList<Metric>();
		
		EnvironmentDamage envDmg = new EnvironmentDamage(env);
		result.add(envDmg);		
		
		ResourceUsageEfficiency resUE = new ResourceUsageEfficiency(env);
		result.add(resUE);
		
		QuotaFulfillment quotaFulfillment = new QuotaFulfillment(agents);
		result.add(quotaFulfillment);
		
		QuotaDisparity quotaDisparity = new QuotaDisparity(agents, env);
		result.add(quotaDisparity);
		
		return result;
	}	

	
	/**
	 * Simulates harvesting runs.
	 * 
	 * @param steps the number of steps to run
	 */
	public static void simulate(Environment env, List<Agent> agents, List<Metric> usedMetrics, EnvironmentView ui) {
		
		List<Agent> aux = new LinkedList<Agent>();
		aux.addAll(agents);
		int maxSteps = env.getInt(MAX_SIMULATION_STEPS);
		int stepsUpdTargets = env.getInt(MAX_STRAT_NO) * env.getInt(TARGET_UPD_FACTOR);
		int stepsUpdPaths = stepsUpdTargets * env.getInt(PATH_UPD_FACTOR);
		int stepsUpdQuota = stepsUpdPaths * env.getInt(QUOTA_RECHECK_FACTOR);
		LinkedList<Double> normHistory = new LinkedList<Double>();
		normHistory.add(0D);
		double normHistoryAbsSum = 0D;		
		
		for (int step = 0; step < maxSteps && (ui == null || ui.isActive()); step++) {
			
			System.out.println("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
			System.out.println("@ STEP " + step + ":\n");			
			 
			env.regenerate();
			currentUtilities.clear();
			Collections.shuffle(aux);
			
			for (Agent agent : aux){
				currentUtilities.add(agent.harvest());
				if (ui != null)
					ui.animateAgent(agent);
			}
			
			for (Agent agent : agents) {
				System.out.println();
				agent.pPrint();
			}
			
			System.out.println("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
			for(Metric m:usedMetrics)
				System.out.println(m.toString());
			System.out.println("@ Quota Norm: " + env.getQuotaNorm());
			
			//	Let the agents evolve; let them grow; bigger, faster, smarter, stronger
			//		Let's not update strategies if it's a quota modifying step
			if ((step+1) % stepsUpdQuota != 0) {
				if ((step+1) % stepsUpdPaths == 0) {
					for (Agent agent : agents) {
						agent.updateStrategiesPaths();
					}
					env.clearPlotHistory();
				} else if ((step+1) % stepsUpdTargets == 0) {
					for (Agent agent : agents) {
						agent.updateStrategiesTargets();
					}
				}
			// quota update step
			} else {
				for (Agent agent : agents) {
					agent.updateQuota();
				}
			}
			
			// update norm history
			double quotaNorm = env.getQuotaNorm();
			double normDelta = quotaNorm - normHistory.getLast();
			normHistory.add(normDelta);
			normHistoryAbsSum += Math.abs(normDelta);
			if (normHistory.size() > NORM_HISTORY_SIZE) {
				normHistoryAbsSum -= Math.abs(normHistory.removeFirst());
			}
			if (normHistoryAbsSum / NORM_HISTORY_SIZE < NORM_HISTORY_EPSILON) {
				System.out.println("\n\n\n################################################################");
				System.out.println("@ SIMULATION CONVERGED after " + step +
						" steps with the quota norm: " + quotaNorm);
				System.out.println("################################################################");
				break;
			}
			
			if (ui != null)
				ui.endOfTurn();
			// Stop - its debug time
			Debug.pauseExec("End of simulation step");

		}
	}	

	
	/**
	 * Displays for each agent its most used strategy in the last AGENT_MEMORY steps
	 */
	public static void displayStratStats(List<Agent> agents) {
		for (Agent agent : agents){
			agent.displayTopStrategy();
		}
	}
	
	
	/**
	 * The execution entry point. Runs the simulation.
	 * 
	 * @param args the configuration file - optional 
	 */
	public static void main(String[] args) {
		
		if (args.length > 1) {
			System.err.println("Usage: java base.Main [<configuration file>]");
			System.exit(1);
		}
		
		Environment env;
		List<Agent> agents;
		List<Metric> metrics;
		EnvironmentView ui = null;
		
		try {
		
			env = (args.length == 0) ? new Environment() : new Environment(args[0]);
			if (env.getInt(ADAPTIVE_AGENTS) == 0 && env.getInt(STATIC_AGENTS) == 0) {
				throw new Exception("No agents to simulate!");
			}
			
			Debug.DEBUG_MODE = env.getBoolean(DEBUG_MODE);
			
			//PointOfOriginStrategyGenerator.testFunctionality(env);
			//LocalOptimumStrategyGenerator.testFunctionality(env);
			agents = genAgents(env, new int[][]{
					{ 20, 30, 10, 17, 23 },
					{ 17, 16, 19, 18, 30 },
					{ 27, 27, 28, 18},
					{ 18, 22, 19, 12, 19}});
			currentUtilities= new ArrayList<Double>();
			metrics = genMetrics(env, agents);					
			
			Collections.sort(agents);
			
			if (env.getBoolean(UI_MODE)) {
				ui = new EnvironmentView(env);
			}
			
			// Stop - its debug time
			Debug.pauseExec("Presimulation break");
			
			// do it!
			if (ui !=null){
				while (!ui.isActive())
					Thread.currentThread().sleep(200);
			}
			simulate(env, agents, metrics, ui);
			
			System.out.println("\n\n");
			System.out.println("################################################################");
			System.out.println("@ CONVERGENCE:");
			System.out.println("################################################################");
			System.out.println("\n\n");
			displayStratStats(agents);
		
		} catch (Exception e) {
			System.err.println("Unexpected exception");
			e.printStackTrace();
			System.exit(2);
			
		} finally {
			if (ui != null) {
				ui.join();
			}
		}
	}
}
